package com.yang.spring.core.bean.definition.parser;

import com.yang.spring.core.annotations.Bean;
import com.yang.spring.core.bean.definition.BeanDefinition;
import com.yang.spring.core.bean.definition.builder.ComponentBeanDefinitionBuilder;
import com.yang.spring.core.bean.definition.builder.ConfigurationBeanDefinitionBuilder;
import com.yang.spring.core.bean.definition.builder.IBeanDefinitionBuilder;
import com.yang.spring.core.bean.definition.builder.ServiceBeanDefinitionBuilder;
import com.yang.spring.core.bean.enums.BeanDefinitionExtendsEnum;
import com.yang.spring.core.bean.enums.BeanTypeEnum;
import com.yang.spring.core.bean.exception.BeanException;
import com.yang.spring.core.exception.enums.ErrorCode;
import com.yang.spring.core.interfaces.InitializingBean;
import com.yang.spring.core.utils.CollectionUtils;
import com.yang.spring.core.utils.StringUtils;

import javax.annotation.PostConstruct;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class BeanDefinitionParser {
    private Map<BeanTypeEnum, IBeanDefinitionBuilder> beanType2BeanDefinitionBuilderMap = new ConcurrentHashMap<>();

    {
        beanType2BeanDefinitionBuilderMap.put(BeanTypeEnum.COMPONENT, new ComponentBeanDefinitionBuilder());
        beanType2BeanDefinitionBuilderMap.put(BeanTypeEnum.SERVICE, new ServiceBeanDefinitionBuilder());
        beanType2BeanDefinitionBuilderMap.put(BeanTypeEnum.CONFIGURATION, new ConfigurationBeanDefinitionBuilder());
    }

    public List<BeanDefinition> parseBeanDefinitionList(Class rootClazz) {
        // 获取应用包路径
        String rootClassName = rootClazz.getName();
        String rootPackage = rootClassName.substring(0, rootClassName.lastIndexOf("."));
        // 获取项目文件路径
        String resourcePath = rootClazz.getResource("").getPath();
        String projectPath = resourcePath.substring(1);
        // 根据包路径，获取改包路径下的所有java文件
        File projectDir = new File(projectPath);
        File[] files = projectDir.listFiles();
        List<String> classNameList = new ArrayList<>();
        for (File file : files) {
            classNameList.addAll(parseClassNames(rootPackage, file));
        }

        List<BeanDefinition> beanDefinitionList = new ArrayList<>();
        try {
            for (String className : classNameList) {
                Class<?> clazz = Class.forName(className);
                List<BeanDefinition> beanDefinitions = parseBeanDefinition(clazz);
                if (CollectionUtils.isEmpty(beanDefinitions)) {
                    continue;
                }
                beanDefinitionList.addAll(beanDefinitions);
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return beanDefinitionList;
    }

    private List<String> parseClassNames(String currentPackage, File file) {
        List<String> classNameList = new ArrayList<>();
        if (file.isFile()) {
            String filename = file.getName();
            if (filename.endsWith(".class")) {
                classNameList.add(currentPackage + "." + filename.substring(0, filename.length() - 6));
            }
        } else {
            for (File listFile : file.listFiles()) {
                classNameList.addAll(parseClassNames(currentPackage + "." + file.getName(), listFile));
            }
        }
        return classNameList;
    }

    public List<BeanDefinition> parseBeanDefinition(Class clazz) {
        // 判断是否有相关的bean注解
        BeanTypeEnum beanType = BeanTypeEnum.getBeanTypeByClazz(clazz);
        if (beanType == null) {
            return null;
        }

        IBeanDefinitionBuilder iBeanDefinitionBuilder = beanType2BeanDefinitionBuilderMap.get(beanType);
        List<BeanDefinition> beanDefinitionList = iBeanDefinitionBuilder.buildBeanDefinition(clazz);

        richPostHandlerOfBeanDefinition(beanDefinitionList);
        return beanDefinitionList;
    }

    private void richPostHandlerOfBeanDefinition(List<BeanDefinition> beanDefinitionList) {
        richPostConstructOfBeanDefinition(beanDefinitionList);
        richInitializingBeanOfBeanDefinition(beanDefinitionList);
        richInitMethod(beanDefinitionList);
    }

    private void richInitMethod(List<BeanDefinition> beanDefinitionList) {
        for (BeanDefinition beanDefinition : beanDefinitionList) {
            if (beanDefinition.getBeanType() != BeanTypeEnum.BEAN) {
                continue;
            }
            Method beanMethod = (Method) beanDefinition.getExtend(BeanDefinitionExtendsEnum.BEAN_METHOD.getValue());
            Bean beanAnnotation = beanMethod.getAnnotation(Bean.class);
            if (beanAnnotation == null || StringUtils.isEmpty(beanAnnotation.initMethod())) {
                continue;
            }
            String initMethodName = beanAnnotation.initMethod();
            Class beanClassType = beanDefinition.getBeanClassType();
            Method initMethod = null;
            for (Method declaredMethod : beanClassType.getDeclaredMethods()) {
                if (initMethodName.equals(declaredMethod.getName()) && declaredMethod.getParameterTypes().length == 0) {
                    initMethod = declaredMethod;
                    break;
                }
            }
            if (initMethod == null) {
                throw new BeanException(ErrorCode.INIT_METHOD_OF_BEAN_TYPE_NOT_EXIST, beanClassType.getName(),
                        initMethodName);
            }
            beanDefinition.putExtend(BeanDefinitionExtendsEnum.INIT_METHOD.getValue(), initMethod);
        }
    }

    private void richInitializingBeanOfBeanDefinition(List<BeanDefinition> beanDefinitionList) {
        for (BeanDefinition beanDefinition : beanDefinitionList) {
            Class beanClassType = beanDefinition.getBeanClassType();
            Method initializingBeanMethod = null;
            if (InitializingBean.class.isAssignableFrom(beanClassType)) {
                for (Method method : beanClassType.getMethods()) {
                    if ("afterPropertiesSet".equals(method.getName()) && method.getParameterTypes().length == 0) {
                        initializingBeanMethod = method;
                        break;
                    }
                }
            }
            if (initializingBeanMethod != null) {
                beanDefinition.putExtend(BeanDefinitionExtendsEnum.INITIALIZING_BEAN_METHOD.getValue(), initializingBeanMethod);
            }
        }
    }

    private void richPostConstructOfBeanDefinition(List<BeanDefinition> beanDefinitionList) {
        for (BeanDefinition beanDefinition : beanDefinitionList) {
            Class beanClassType = beanDefinition.getBeanClassType();
            Method[] methods = beanClassType.getDeclaredMethods();
            Method postConstructMethod = null;
            int postConstructMethodCount = 0;
            for (Method method : methods) {
                PostConstruct postConstruct = method.getAnnotation(PostConstruct.class);
                if (postConstruct != null) {
                    postConstructMethodCount ++;
                    postConstructMethod = method;
                }
            }
            if (postConstructMethodCount == 0) {
                continue;
            }
            if (postConstructMethodCount > 1) {
                throw new BeanException(ErrorCode.BEAN_TYPE_HAS_MORE_THAN_ONE_POST_CONSTRUCT);
            }
            beanDefinition.putExtend(BeanDefinitionExtendsEnum.POST_CONSTRUCT_METHOD.getValue(), postConstructMethod);
        }
    }
}
